package org.budo.time;

import java.text.DateFormat;
import java.text.ParseException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.budo.time.impl.BudoTimeFormat;
import org.budo.time.impl.BudoTimePoint;
import org.budo.time.impl.BudoTimeSpan;
import org.budo.time.impl.DateFormatFactory;

import lombok.extern.slf4j.Slf4j;

/**
 * 时间工具类
 *
 * @author lmw
 */
@Slf4j
public class Time {
    private static final String NUMBER_REGEX = "^[0-9]*$";

    public static final DateFormat yyyy_MM_dd_HH_mm_ss = dateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 默认支持的几种时间格式
     */
    public static final String[] DEFAULT_TIME_FORMATS = { //
            "yyyy-MM-dd HH:mm:ss", //
            "yyyy-MM-dd", //
            "HH:mm:ss", //
            "HH:mm:ss SSS", //
            "HH:mm:ss.SSS", //
            "MM-dd HH:mm", //
            "yyyyMMddHHmmss", //
            "yyyy/MM/dd HH:mm:ss", //
            "yyyy-MM-dd HH:mm:ss SSS", //
            "yyyy-MM-dd HH:mm:ss.SSS", //
            "yyyy-MM-dd HH:mm:ss.SS", //
            "yyyy-MM-dd HH:mm:ss.S", //
            "yyyyMMdd.HHmmss.SSS", // 20210623.200122.784
            "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" //
    };

    public static TimeSpan span(TimePoint from, TimePoint to) {
        return new BudoTimeSpan(from, to);
    }

    public static TimeSpan span(Date from, Date to) {
        return new BudoTimeSpan(from, to);
    }

    public static TimeSpan span(Long milliSeconds) {
        return new BudoTimeSpan(milliSeconds);
    }

    /**
     * 返回服务器当前时间, 不同服务器的机器时间可能不同
     */
    public static TimePoint now() {
        return new BudoTimePoint(new Date());
    }

    /**
     * 明日此时
     */
    public static TimePoint tomorrow() {
        return now().plusDay(1);
    }

    /**
     * 昨日此时
     */
    public static TimePoint yesterday() {
        return now().plusDay(-1);
    }

    /**
     * 将Date转为TimePoint
     *
     * @param date 传入空值时不会报错
     */
    public static TimePoint when(Date date) {
        return new BudoTimePoint(date);
    }

    public static TimePoint when(Instant instant) {
        return when(instant.toEpochMilli());
    }

    public static TimePoint when(LocalDateTime localDateTime) {
        if (null == localDateTime) {
            return new BudoTimePoint(null);
        }

        ZoneId defaultZoneId = ZoneId.systemDefault();
        Instant instant = localDateTime.atZone(defaultZoneId).toInstant();
        Date date = Date.from(instant);
        return new BudoTimePoint(date);
    }

    // 假如当前是12月 Calendar.get(Calendar.MONTH) 是 11
    public static TimePoint when(LocalDate localDate) {
        if (null == localDate) {
            return new BudoTimePoint(null);
        }

        int year = localDate.getYear();
        int month = localDate.getMonthValue() - 1; // 月份 -1
        int day = localDate.getDayOfMonth();

        return Time.now() //
                .setDate(year, month, day) //
                .datePart(); // 日期部分 不要时间
    }

    public static TimePoint when(LocalTime localTime) {
        if (null == localTime) {
            return new BudoTimePoint(null);
        }

        int hour = localTime.getHour();
        int minute = localTime.getMinute();
        int second = localTime.getSecond();
        int milliSecond = localTime.getNano() / 1000;

        return Time.now() //
                .setTime(hour, minute, second, milliSecond) //
                .timePart();
    }

    /**
     * milliSeconds 转 TimePoint
     */
    public static TimePoint when(Number milliSeconds) {
        if (null == milliSeconds) {
            return new BudoTimePoint(null);
        }

        return when(new Date(milliSeconds.longValue()));
    }

    /**
     * 指定时间字符串和解析格式，返回对应时间
     */
    public static TimePoint when(String time, DateFormat dateFormat) {
        if (null == time || time.trim().isEmpty()) {
            return new BudoTimePoint(null);
        }

        try {
            synchronized (dateFormat) {
                Date date = dateFormat.parse(time);
                return new BudoTimePoint(date);
            }
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 指定时间字符串和解析格式，返回对应时间
     */
    public static TimePoint when(String time, TimeFormat timeFormat) {
        return timeFormat.parse(time);
    }

    /**
     * @param time   时间
     * @param format 格式
     */
    public static TimePoint when(String time, String format) {
        TimeFormat timeFormat = format(format);
        return when(time, timeFormat);
    }

    public static TimePoint parse(String time, String format) {
        return when(time, format);
    }

    public static TimePoint parse(String time, DateFormat dateFormat) {
        return when(time, dateFormat);
    }

    public static TimePoint parse(String time, TimeFormat timeFormat) {
        return when(time, timeFormat);
    }

    public static TimeFormat timeFormat(String pattern) {
        return new BudoTimeFormat(pattern);
    }

    /**
     * 返回时间解析格式
     */
    public static TimeFormat format(String pattern) {
        return timeFormat(pattern);
    }

    /**
     * 返回时间解析格式
     */
    public static DateFormat dateFormat(String pattern) {
        DateFormatFactory dateFormatFactory = DateFormatFactory.getCurrent();
        return dateFormatFactory.getDateFormat(pattern);
    }

    public static TimeSpan between(Date _this, Date _that) {
        return when(_this).between(_that);
    }

    public static int compare(Date _this, Date _that) {
        if (null == _this && null == _that) {
            return 0;
        }

        if (null == _this) {
            return -1;
        }

        if (null == _that) {
            return 1;
        }

        return _this.compareTo(_that);
    }

    public static TimePoint when(Object value) {
        return when(value, DEFAULT_TIME_FORMATS);
    }

    public static TimePoint when(Object value, String[] timeFormats) {
        if (null == value) {
            return new BudoTimePoint(null);
        }

        String str = (value + "").trim();
        if ("".equalsIgnoreCase(str) || "null".equalsIgnoreCase(str) || "undefined".equalsIgnoreCase(str)) {
            return new BudoTimePoint(null);
        }

        if (value instanceof TimePoint) {
            return new BudoTimePoint(((TimePoint) value).toDate());
        }

        if (value instanceof Date) {
            return new BudoTimePoint((Date) value);
        }

        if (value instanceof Instant) {
            return when((Instant) value);
        }

        if (value instanceof LocalDate) {
            return when((LocalDate) value);
        }

        if (value instanceof LocalTime) {
            return when((LocalTime) value);
        }

        if (value instanceof LocalDateTime) {
            return when((LocalDateTime) value);
        }

        // 数字
        if (value instanceof Number) {
            return new BudoTimePoint(new Date(((Number) value).longValue()));
        }

        // 字符串形式的数字
        if (value instanceof String && ((String) value).matches(NUMBER_REGEX)) {
            long parseLong = Long.parseLong((String) value);
            return new BudoTimePoint(new Date(parseLong));
        }

        // 根据时间格式，解析字符串为时间
        for (String timeFormat : timeFormats) {
            String val = ((String) value).trim();

            if (timeFormat.replace("\'", "").length() != val.length()) {
                continue; // 长度相同才会 parse
            }

            try {
                TimePoint timePoint = Time.when(val, timeFormat);
                return timePoint;
            } catch (Throwable e) {
                // 解析失败，try掉，尝试下一个格式，性能问题
            }
        }

        throw new IllegalArgumentException("#227 Unsupported TIME_FORMAT, value = " + value + ", type = " + value.getClass());
    }

    public static TimePoint min(Date... array) {
        if (null == array || array.length < 1) {
            return null;
        }

        Date min = null;
        for (int i = 0; i < array.length; i++) {
            if (null == min || (null != array[i] && array[i].before(min))) {
                min = array[i];
            }
        }

        return when(min);
    }

    public static TimePoint min(Object... array) {
        if (null == array || array.length < 1) {
            return null;
        }

        TimePoint min = null;
        for (int i = 0; i < array.length; i++) {
            TimePoint each = when(array[i]);
            if (null == min || (null != array[i] && each.isBefore(min))) {
                min = each;
            }
        }

        return min;
    }

    public static TimePoint max(Date... array) {
        if (null == array || array.length < 1) {
            return null;
        }

        Date max = null;
        for (int i = 0; i < array.length; i++) {
            if (null == max || (null != array[i] && array[i].after(max))) {
                max = array[i];
            }
        }

        return when(max);
    }

    public static TimePoint max(Object... array) {
        if (null == array || array.length < 1) {
            return null;
        }

        TimePoint min = null;
        for (int i = 0; i < array.length; i++) {
            TimePoint each = when(array[i]);
            if (null == min || (null != array[i] && each.isAfter(min))) {
                min = each;
            }
        }

        return min;
    }

    /**
     * 生产一个时间序列数组
     * 
     * @param from      起点
     * @param to        终点
     * @param increment 间隔数
     * @param timeUnit  间隔单位
     * @return 时间点的数组
     */
    public static List<TimePoint> sequence(TimePoint from, TimePoint to, Integer increment, Integer timeUnit) {
        List<TimePoint> list = new ArrayList<TimePoint>();

        TimePoint current = Time.when(from);
        while (true) {
            list.add(current); // 起点

            current = current.timeFieldPlus(timeUnit, increment); // 累加

            if (current.isAfter(to)) { // 如果超出终点就
                break;
            }
        }

        return list;
    }

    public static TimePoint daysAgo(Number day) {
        return Time.now().minusDay(day);
    }

    public static Boolean isTimeType(Class<?> type) {
        if (null == type) {
            return false;
        }

        return Date.class.isAssignableFrom(type);
    }

    public static Boolean isToday(Date date) {
        return isSameDay(new Date(), date);
    }

    private static Boolean isSameDay(Date date1, Date date2) {
        if (null == date1 || null == date2) {
            log.error("#393 isSameDay, date1=" + date1 + ", date2=" + date2);
            return false;
        }

        return when(date1).datePart().isEqual(when(date2).datePart());
    }
}
